package org.budo.dubbo.config.builder;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * @author limingwei
 * @date 2016年6月7日 上午11:03:29
 */
public class ReferenceBuilder {
    private static final Map<String, Object> _REFERENCE_CACHE = new ConcurrentHashMap<String, Object>();

    private Object applicationConfig = null;

    private Object registryConfig;

    private Object referenceConfig;

    private Object referenceInstance;

    private String url;

    private String interfaceName;

    private Boolean generic = false;

    private Integer timeout = 25 * 1000;

    private String group;

    private String version;

    public ReferenceBuilder() {
    }

    public ReferenceBuilder(String host, Integer port, String interfaceName) {
        this.setInterface(interfaceName);
        this.setUrl("dubbo://" + host + ":" + port + "/" + interfaceName);
    }

    public ReferenceBuilder setRegistryConfig(Object registryConfig) {
        this.registryConfig = registryConfig;
        return this;
    }

    public ReferenceBuilder setVersion(String version) {
        this.version = version;
        return this;
    }

    public ReferenceBuilder setApplicationConfig(Object applicationConfig) {
        this.applicationConfig = applicationConfig;
        return this;
    }

    public ReferenceBuilder setTimeout(Integer timeout) {
        this.timeout = timeout;
        return this;
    }

    public ReferenceBuilder setUrl(String url) {
        this.url = url;
        return this;
    }

    public ReferenceBuilder setInterface(String interfaceName) {
        this.interfaceName = interfaceName;
        return this;
    }

    public ReferenceBuilder setGeneric(Boolean generic) {
        this.generic = generic;
        return this;
    }

    public ReferenceBuilder setGroup(String group) {
        this.group = group;
        return this;
    }

    public String getUrl() {
        return this.url;
    }

    public Integer getTimeout() {
        return this.timeout;
    }

    public Object getReference() {
        String cacheKey = "generic=" + this.generic //
                + ", applicationConfig.name=" + ((com.alibaba.dubbo.config.ApplicationConfig) this.applicationConfig).getName() //
                + ", ur=" + this.getUrl() //
                + ", interfaceName=" + this.interfaceName //
                + ", group=" + this.group //
                + ", version=" + this.version //
                + ", timeout=" + this.timeout;

        if (null != this.registryConfig) {
            cacheKey += ", registryConfig.address=" + ((com.alibaba.dubbo.config.RegistryConfig) this.registryConfig).getAddress();
        }

        try {
            ReferenceBuilder referenceBuilder = (ReferenceBuilder) _REFERENCE_CACHE.get(cacheKey);
            if (null != referenceBuilder) {
                return referenceBuilder.referenceInstance;
            }

            // 初始化
            this.referenceInstance = this.buildReference();
            _REFERENCE_CACHE.put(cacheKey, this);
            return this.referenceInstance;
        } catch (Throwable e) {
            throw new RuntimeException("#118 " + e + ", cacheKey=" + cacheKey, e);
        }
    }

    public Object buildReference() {
        this.referenceConfig = ReferenceBuilder.this.buildReferenceConfig();

        try {
            ExecutorService executorService = Executors.newSingleThreadExecutor();
            Future<Object> future = executorService.submit(new Callable<Object>() {
                public Object call() throws Exception {
                    return ((com.alibaba.dubbo.config.ReferenceConfig) referenceConfig).get();
                }
            });
            return future.get(this.getTimeout(), TimeUnit.MILLISECONDS);
        } catch (Throwable e) {
            ((com.alibaba.dubbo.config.ReferenceConfig) this.referenceConfig).destroy();
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("rawtypes")
    private Object buildReferenceConfig() {
        try {
            com.alibaba.dubbo.config.ReferenceConfig referenceConfig = new com.alibaba.dubbo.config.ReferenceConfig();

            if (null != this.applicationConfig) {
                referenceConfig.setApplication((com.alibaba.dubbo.config.ApplicationConfig) this.applicationConfig);
            }

            if (null != this.getUrl()) {
                referenceConfig.setUrl(this.getUrl());
            }

            if (null != this.interfaceName) {
                referenceConfig.setInterface(this.interfaceName);
            }

            if (null != this.generic) {
                referenceConfig.setGeneric(this.generic);
            }

            if (null != this.getTimeout()) {
                referenceConfig.setTimeout(this.getTimeout());
            }

            if (null != this.group) {
                referenceConfig.setGroup(this.group);
            }

            if (null != this.version) {
                referenceConfig.setVersion(this.version);
            }

            if (null != this.registryConfig) {
                referenceConfig.setRegistry((com.alibaba.dubbo.config.RegistryConfig) this.registryConfig);
            }

            return referenceConfig;
        } catch (Throwable e) {
            throw new RuntimeException("#182 buildReference error, e=" + e, e);
        }
    }
}